// Copyright (C) 2008 Victor Marzo
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//
// Linking this library statically or dynamically with other modules is
// making a combined work based on this library.  Thus, the terms and
// conditions of the GNU General Public License cover the whole
// combination.
// 
// As a special exception, the copyright holders of this library give you
// permission to link this library with independent modules to produce an
// executable, regardless of the license terms of these independent
// modules, and to copy and distribute the resulting executable under
// terms of your choice, provided that you also meet, for each linked
// independent module, the terms and conditions of the license of that
// module.  An independent module is a module which is not derived from
// or based on this library.  If you modify this library, you may extend
// this exception to your version of the library, but you are not
// obligated to do so.  If you do not wish to do so, delete this
// exception statement from your version.

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Collections.Generic;
using System.IO;

namespace NForth
{
    public class Engine
    {
        private static bool show_info = true;
        public static int compiler_state = 0;

        public static void Init()
        {
            Dictionary.ClearNamespaces();
            Dictionary.AddNamespace("System");
            Dictionary.AddNamespace("System.Reflection");
            Dictionary.AddNamespace("System.Reflection.Emit");

            Compiler.Init();
            KernelInit();
        }

        #region Kernel
        private static void Create(string word, string method)
        {
            Dictionary.Create(word, typeof(Engine).GetMethod(method, new Type[] { }));
        }

        private static void Create(string word, string method, int immediate)
        {
            Dictionary.Create(word, typeof(Engine).GetMethod(method));
            Dictionary.Latest.Immediate = immediate;
        }

        public static void KernelInit()
        {
            Create("create", "Create");
            Create("finish", "Finish");
            Create("s\"", "NextString", 1);
            Create("new", "New");
            Create("latest", "Latest");
            Create("'", "Tick");
            Create("see", "See");
            Create("opcode,", "Emit");
            Create("call,", "EmitCall");
            Create("literal", "EmitLiteral", 1);
            Create(",", "EmitLiteral");
            Create("type,", "EmitType");
            Create("field,", "EmitField");
            Create("label,", "EmitLabel");
            Create("immediate", "Immediate", 1);
            Create("[", "CompilingOff", 1);
            Create("]", "CompilingOn");
            Create("@", "Read");
            Create("if", "If", 1);
            Create("else", "Else", 1);
            Create("then", "Then", 1);
            Create("include", "Include");
        }

        public static void Create()
        {
            Dictionary.Create(Parser.NextWord());
            Dictionary.Latest.Hidden = 1;
            Compiler.BeginMethod(Dictionary.Latest.Name);
        }

        public static void Finish()
        {
            Dictionary.Latest.Method = Compiler.EndMethod();
            Dictionary.Latest.Hidden = 0;
        }

        public static void NextString()
        {
            string s = Parser.ReadUntil("\"");
            DataStack.Push(s);
            if (compiler_state != 0)
                EmitLiteral();
        }

        public static void New()
        {
            string word_name = Parser.NextWord();
            string[] args = word_name.Split('`');

            // find type
            Type class_type = Dictionary.FindType(args[0]);
            if (class_type == null)
                return;

            // find constructor
            Type[] types = new Type[args.Length - 1];
            for (int i = 0; i < types.Length; i++)
                types[i] = Dictionary.FindType(args[i + 1]);
            ConstructorInfo constructor_info = class_type.GetConstructor(types);
            if (constructor_info == null)
                return;

            // method parameters
            object[] parameters = new object[constructor_info.GetParameters().Length];
            for (int i = 0; i < parameters.Length; i++)
                parameters[i] = DataStack.Pop();

            // invoke constructor
            object obj = constructor_info.Invoke(parameters);
            DataStack.Push(obj);
        }

        public static void Tick()
        {
            string word_name = Parser.NextWord();
            MethodInfo method_info = Dictionary.FindMethod(word_name);

            if (method_info != null)
            {
                DataStack.Push(method_info);
                return;
            }

            FieldInfo field_info = Dictionary.FindField(word_name);
            if (field_info != null)
            {
                DataStack.Push(field_info);
                return;
            }

            Type type = Dictionary.FindType(word_name);
            if (type != null)
            {
                DataStack.Push(type);
                return;
            }

            DataStack.Push(null);
        }

        public static void See()
        {
            string word_name = Parser.NextWord();
            MethodInfo method_info = Dictionary.FindMethod(word_name);
            if (method_info != null)
                Console.Write(Disassembler.Disassemble(method_info));
        }

        public static void Emit()
        {
            OpCode opcode = (OpCode)DataStack.Pop();
            Compiler.Emit(opcode);
        }

        public static void EmitCall()
        {
            MethodInfo method = (MethodInfo)DataStack.Pop();
            Compiler.EmitCall(method);
        }

        public static void EmitLiteral()
        {
            object o = DataStack.Pop();
            Compiler.EmitLiteral(o);

            Type t = o.GetType();
            if (t.Name == "RuntimeType")
                t = t.BaseType;

            if (t == typeof(Type))
                Compiler.EmitCall(typeof(Type).GetMethod("GetTypeFromHandle"));
            if (t.IsValueType)
                Compiler.EmitType(OpCodes.Box, o.GetType());
            Compiler.EmitCall(typeof(DataStack).GetMethod("Push"));
        }

        public static void EmitType()
        {
            Type type = (Type)DataStack.Pop();
            OpCode opcode = (OpCode)DataStack.Pop();
            Compiler.EmitType(opcode, type);
        }

        public static void EmitField()
        {
            FieldInfo field_info = (FieldInfo)DataStack.Pop();
            OpCode opcode = (OpCode)DataStack.Pop();
            Compiler.EmitField(opcode, field_info);
        }

        public static void EmitLabel()
        {
            Label label = (Label)DataStack.Pop();
            OpCode opcode = (OpCode)DataStack.Pop();
            Compiler.EmitLabel(opcode, label);
        }

        public static void Immediate()
        {
            Dictionary.Latest.Immediate = 1;
        }

        public static void CompilingOn()
        {
            compiler_state = 1;
        }

        public static void CompilingOff()
        {
            compiler_state = 0;
        }

        public static void Read()
        {
            FieldInfo field_info = (FieldInfo)DataStack.Pop();
            DataStack.Push(field_info.GetValue(null));
        }

        public static void If()
        {
            Label else_label = Compiler.DefineLabel();
            Label endif_label = Compiler.DefineLabel();

            Compiler.EmitCall(typeof(DataStack).GetMethod("Pop"));
            Compiler.EmitType(OpCodes.Unbox_Any, typeof(int));
            Compiler.EmitLabel(OpCodes.Brfalse, else_label);

            ReturnStack.Push(else_label);
            ReturnStack.Push(endif_label);
        }

        public static void Else()
        {
            Label endif_label = (Label)ReturnStack.Pop();
            Label else_label = (Label)ReturnStack.Pop();

            Compiler.EmitLabel(OpCodes.Br, endif_label);
            Compiler.MarkLabel(else_label);

            ReturnStack.Push(null);
            ReturnStack.Push(endif_label);
        }

        public static void Then()
        {
            Label endif_label = (Label)ReturnStack.Pop();
            object else_label = ReturnStack.Pop();

            if (else_label != null)
                Compiler.MarkLabel((Label)else_label);

            Compiler.MarkLabel(endif_label);
        }

        public static void Include()
        {
            Include(Parser.NextWord());
        }
        #endregion

        public static void Include(string file)
        {
            TextReader old_in = Console.In;
            StreamReader sr = new StreamReader(file);

            StringReader new_in = new StringReader(sr.ReadToEnd());
            Console.SetIn(new_in);

            show_info = false;
            Interpret();
            show_info = true;

            Console.SetIn(old_in);
            new_in.Close();

            sr.Close();
        }

        #region Interpret
        public static void Interpret()
        {
            while (true)
            {
                try
                {
                    string word_name = Parser.NextWord();

                    if (word_name != "")
                        if (compiler_state == 0)
                        {
                            InterpretWord(word_name);

                            if (Parser.EndOfLine && show_info)
                            {
                                DataStack.Print();
                                Console.WriteLine(" ok.");
                            }
                        }
                        else
                            CompileWord(word_name);
                }
                catch (EndOfStreamException)
                {
                    return;
                }
                catch (Exception e)
                {
                    string err;
                    if (e.InnerException != null)
                        err = e.InnerException.Message;
                    else
                        err = e.Message;

                    Console.WriteLine(" EXCEPTION: " + err);
                }
            }
        }

        public static void InterpretWord(string word_name)
        {
            MethodInfo method_info = Dictionary.FindMethod(word_name);
            if (method_info != null)
            {
                ExecuteMethod(method_info);
                return;
            }

            FieldInfo field_info = Dictionary.FindField(word_name);
            if (field_info != null)
            {
                DataStack.Push(field_info);
                return;
            }

            int number;
            if (int.TryParse(word_name, out number))
                DataStack.Push(number);
            else
                Console.Write(" Unknown `" + word_name + "'");
        }

        public static void ExecuteMethod(MethodInfo method_info)
        {
            // class instance
            object obj = null;
            if (!method_info.IsStatic)
                obj = DataStack.Pop();

            // method parameters
            ParameterInfo[] parameters = method_info.GetParameters();
            object[] args = new object[parameters.Length];
            for (int i = 0; i < args.Length; i++)
                if (parameters[i].ParameterType == typeof(bool))
                    args[i] = Convert.ToBoolean(DataStack.Pop());
                else
                    args[i] = DataStack.Pop();

            // invoke method
            object result = method_info.Invoke(obj, args);

            // push result
            if (method_info.ReturnType != typeof(void))
                DataStack.Push(result);
        }

        public static void CompileWord(string word_name)
        {
            MethodInfo method_info = Dictionary.FindMethod(word_name);
            if (method_info != null)
            {
                Word word = Dictionary.FindWord(word_name);
                if (word != null && word.Immediate != 0)
                    method_info.Invoke(null, null);
                else
                {
                    if (!method_info.IsStatic)
                        Compiler.EmitCall(Dictionary.FindMethod("NForth.DataStack.Pop"));

                    foreach (ParameterInfo p in method_info.GetParameters())
                    {
                        Compiler.EmitCall(Dictionary.FindMethod("NForth.DataStack.Pop"));
                        if (p.ParameterType == typeof(bool))
                        {
                            Compiler.EmitType(OpCodes.Unbox_Any, typeof(int));
                            Compiler.EmitCall(typeof(Convert).GetMethod("ToBoolean", new Type[] { typeof(int) }));
                        }
                        else if (p.ParameterType.IsValueType)
                            Compiler.EmitType(OpCodes.Unbox_Any, p.ParameterType);
                    }

                    Compiler.EmitCall(method_info);

                    if (method_info.ReturnType != typeof(void))
                        if (method_info.ReturnType.IsValueType)
                        {
                            Compiler.EmitType(OpCodes.Box, method_info.ReturnType);
                            Compiler.EmitCall(Dictionary.FindMethod("NForth.DataStack.Push"));
                        }
                        else
                            Compiler.EmitCall(Dictionary.FindMethod("NForth.DataStack.Push"));
                }

                return;
            }

            FieldInfo field_info = Dictionary.FindField(word_name);
            if (field_info != null)
            {
                Compiler.EmitLiteral(field_info);
                Compiler.EmitCall(typeof(DataStack).GetMethod("Push"));
                return;
            }

            int number;
            if (int.TryParse(word_name, out number))
            {
                Compiler.EmitLiteral(number);
                Compiler.EmitType(OpCodes.Box, typeof(int));
                Compiler.EmitCall(typeof(DataStack).GetMethod("Push"));
                return;
            }
            else
                Console.WriteLine(" Unknown `" + word_name + "'");
        }
        #endregion

        public static void kk()
        {
            DataStack.Push(new Compiler());
        }
    }
}
